using System;
using UnityEngine;
using Object = UnityEngine.Object;

public class GameController : MonoBehaviour
{
    public Action<GameState> OnGameStateChanged { get; set; }
    public float Time { get; private set; }
    private float m_timeScale = 1f;

    private static readonly Type[] ResettableTypes;

    static GameController()
    {
        ResettableTypes = Utils.FindAllTypeByInterface(typeof(IResettable), typeof(MonoBehaviour));
    }

    public float TimeScale
    {
        get => m_timeScale;
        set => m_timeScale = Mathf.Clamp(value, 0, Mathf.Infinity);
    }

    public GameState State { get; private set; } = GameState.Idle;
    public bool Paused => State == GameState.Paused;
    public bool Started => State != GameState.Idle;
    public bool Playing => State == GameState.Playing;

    public float DeltaTime
    {
        get;
        private set;
    }

    private void Update()
    {
        DeltaTime = UnityEngine.Time.deltaTime * m_timeScale; 
        if (Playing)
        {
            Time += UnityEngine.Time.deltaTime * TimeScale;
        }
    }

    private void CallStateChangedCallback()
    {
        OnGameStateChanged?.Invoke(State);
    }

    public bool StartGame()
    {
        if (Paused)
        {
            Debug.LogWarning("StartGame was called when paused. Call ResumeGame instead.");
        }

        if (Started)
        {
            return false;
        }

        State = GameState.Playing;
        CallStateChangedCallback();
        return true;
    }

    public bool Resume(float time = Mathf.NegativeInfinity)
    {
        if (!Paused) return false;
        if (!float.IsPositiveInfinity(time) && !float.IsNegativeInfinity(time))
        {
            Time = Mathf.Clamp(time, 0, Mathf.Infinity);
        }

        State = GameState.Playing;
        CallStateChangedCallback();
        return true;
    }

    public bool StopGame()
    {
        if (!Started) return false;
        State = GameState.Stopped;
        CallStateChangedCallback();
        return true;
    }

    public void ResetGame()
    {
        State = GameState.Idle;
        Time = 0;
        foreach (Type resettableType in ResettableTypes)
        {
            foreach (Object obj in FindObjectsOfType(resettableType))
            {
                if (!(obj is IResettable resettable)) continue;
                resettable.Reset();
            }
        }
        CallStateChangedCallback();
    }
}

public enum GameState
{
    Idle,
    Playing,
    Paused,
    Stopped
}

public interface IResettable
{
    void Reset();
}